package Logica;

import java.awt.Rectangle;

import General.Constantes;
import Logica.Celdas.Celda;
import Logica.Enemigos.Enemigo;
import Logica.PowerUps.PowerUp;

/**
 * Esta clase es la encargada de controla las colisiones que se producen en el juego. Utiliza al
 * MapaVisible y al Mapa para verificar si hay colisiones entre los distintos objetos.
 * 
 * @author Mariano Rodecker
 * @author Matias Varela
 * 
 */

public class ControlColisiones {
	/* ################## ATRIBUTOS DE INSTANCIA ######################## */
	protected MapaVisible mapaV;

	/* ################## CONSTRUCTORES ######################## */
	/**
	 * Constructor para el Control de colisiones.
	 * 
	 * @param mapaV
	 */
	public ControlColisiones(MapaVisible mapaV) {
		this.mapaV = mapaV;
	}

	/* ################## CONSULTAS ######################## */

	/**
	 * Retorna si un determinado objeto obj colisiona con alguna de las celdas del mapa suponiendo
	 * que el mismo se moviera hacia la direccion dir pasada por parametro. Retorna la celda con la
	 * cual colision obj en caso que se produzca tal colision; en otro caso retorna null.
	 * 
	 * @param obj
	 *            - es el objeto a verificar colision.
	 * @param dir
	 *            - es la direccion en la cual obj quiere moverse.
	 * @return la celda con la cual colision obj en caso que se produzca tal colision; en otro caso
	 *         retorna null.
	 */
	public Celda getColisionCelda(Objeto obj, int dir) {
		Mapa mapa = mapaV.getMapa();
		/* obtengo la posicion i,j del obj en el mapa. */

		int cant_i = (int) Math.ceil((double) obj.getSizeY() / (double) Constantes.sizeCelda); // Math.ceil
																								// funcion
																								// Techo
		int obj_i1 = obj.getY() / Constantes.sizeCelda;
		int obj_i2 = obj_i1 + (cant_i-1);

		int cant_j = (int) Math.ceil((double) obj.getSizeX() / (double) Constantes.sizeCelda); // Math.ceil
																								// funcion
																								// Techo
		int obj_j1 = obj.getX() / Constantes.sizeCelda;
		int obj_j2 = obj_j1 + (cant_j - 1);

		// r1 corresponde al rectangulo del obj corrido segun la direccion dir.
		Rectangle r1 = new Rectangle();
		/*
		 * Son las tres celdas adyacentes a obj segun la direccion dir. Por ejemplo si dir indica
		 * que obj intenta moverse en direccion hacia arriba entonces las 3 celdas con las que
		 * pudiera colisionar son las celdas que estan por encima de obj en el mapa es decir una
		 * fila mas arriba que obj_i
		 */
		Celda[] celdasAdy = new Celda[5];
		switch (dir) {
		/*
		 * Segun dir, inicializo las celdas adyacentes y el rectangulo correspondiente a obj.
		 */
			case Constantes.dir_bot:
				celdasAdy[0] = mapa.getCelda(obj_i2 + 1, obj_j1);
				celdasAdy[1] = mapa.getCelda(obj_i2 + 1, obj_j2 + 1);
				celdasAdy[2] = mapa.getCelda(obj_i2 + 1, obj_j1 - 1);
				celdasAdy[3] = null;
				r1.setBounds(obj.getX(), obj.getY() + 1, obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_top:
				celdasAdy[0] = mapa.getCelda(obj_i1 - 1, obj_j1);
				celdasAdy[1] = mapa.getCelda(obj_i1 - 1, obj_j2 + 1);
				celdasAdy[2] = mapa.getCelda(obj_i1 - 1, obj_j1 - 1);
				celdasAdy[3] = null;
				r1.setBounds(obj.getX(), obj.getY() - 1, obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_left:
				celdasAdy[0] = mapa.getCelda(obj_i1, obj_j1 - 1);
				celdasAdy[1] = mapa.getCelda(obj_i2, obj_j1 - 1);
				celdasAdy[2] = mapa.getCelda(obj_i1 - 1, obj_j1 - 1);
				celdasAdy[3] = mapa.getCelda(obj_i2 + 1, obj_j1 - 1);
				r1.setBounds(obj.getX() - 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_right:
				celdasAdy[0] = mapa.getCelda(obj_i1, obj_j2 + 1);
				celdasAdy[1] = mapa.getCelda(obj_i2, obj_j2 + 1);
				celdasAdy[2] = mapa.getCelda(obj_i1 - 1, obj_j2 + 1);
				celdasAdy[3] = mapa.getCelda(obj_i2 + 1, obj_j2 + 1);
				r1.setBounds(obj.getX() + 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			default:
				celdasAdy[0] = mapa.getCelda(obj_i1, obj_j1);
				celdasAdy[1] = mapa.getCelda(obj_i2, obj_j2);
				r1.setBounds(obj.getX(), obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
		}

		/*
		 * Este es el rectangulo correspondiente a una de las celdas adyacentes.
		 */
		Rectangle r2 = new Rectangle();
		Celda c, toReturn = null;
		Rectangle maxIntersection = null, rIntersection = null;
		/*
		 * Recorro las celdas adyacentes y verifico si alguna colisiona con obj. Esto es que
		 * verifico si los rectangulos r1 y r2 se intersectan. Si se produce una interseccion
		 * almaceno esa intersection y sigo recorriendo retornando finalmente la celda con la cual
		 * se produce una mayor colision.
		 */
		for (int i = 0; i < celdasAdy.length; i++) {
			c = celdasAdy[i];
			if (c != null) {
				r2.setBounds(c.getX(), c.getY(), c.getSizeX(), c.getSizeY());
				if (r1.intersects(r2)) {
					if (maxIntersection == null) {
						maxIntersection = r1.intersection(r2);
						toReturn = c;

					} else {
						rIntersection = r1.intersection(r2);
						if (rIntersection.getWidth() > maxIntersection.getWidth()) {
							maxIntersection = rIntersection;
							toReturn = c;
						}

					}

				}
			}
		}

		return toReturn;

	}

	/**
	 * Retorna si un determinado objeto obj colisiona con el Mario suponiendo que el mismo se
	 * moviera hacia la direccion dir pasada por parametro. Retorna si se produjo colision.
	 * 
	 * @param obj
	 *            - es el objeto a verificar colision.
	 * @param dir
	 *            - es la direccion en la cual obj quiere moverse.
	 * @return si se produjo colision con Mario.
	 */

	public boolean getColisionMario(Objeto obj, int dir) {
		Rectangle r1 = new Rectangle(), r2 = new Rectangle();
		switch (dir) {
			case Constantes.dir_right:
				r1.setBounds(obj.getX() + 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_left:
				r1.setBounds(obj.getX() - 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_top:
				r1.setBounds(obj.getX(), obj.getY() - 1, obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_bot:
				r1.setBounds(obj.getX(), obj.getY() + 1, obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_nada_left:
				r1.setBounds(obj.getX(), obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_nada_right:
				r1.setBounds(obj.getX(), obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
		}

		r2.setBounds(mapaV.getMario().getX(), mapaV.getMario().getY(), mapaV.getMario().getSizeX(), mapaV.getMario().getSizeY());
		return r1.intersects(r2);
	}

	/**
	 * Retorna si un determinado objeto obj colisiona con algun powerup del mapaVisible suponiendo
	 * que el mismo se moviera hacia la direccion dir pasada por parametro. Retorna el powerup que
	 * colisiono con el objeto, retorna null si no hay colision.
	 * 
	 * @param obj
	 *            - es el objeto a verificar colision.
	 * @param dir
	 *            - es la direccion en la cual obj quiere moverse.
	 * @return Retorna el powerup que colisiono con el objeto.
	 */
	public PowerUp getColisionPowerUp(Objeto obj, int dir) {
		Rectangle r1 = new Rectangle(), r2 = new Rectangle();
		switch (dir) {
			case Constantes.dir_nada_left:
				return null;
			case Constantes.dir_nada_right:
				return null;
			case Constantes.dir_right:
				r1.setBounds(obj.getX() + 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_left:
				r1.setBounds(obj.getX() - 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_top:
				r1.setBounds(obj.getX(), obj.getY() - 1, obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_bot:
				r1.setBounds(obj.getX(), obj.getY() + 1, obj.getSizeX(), obj.getSizeY());
				break;
		}

		for (PowerUp p : mapaV.getPowerUps()) {
			r2.setBounds(p.getX(), p.getY(), p.getSizeX(), p.getSizeY());
			if (r1.intersects(r2))
				return p;
		}
		return null;
	}

	/**
	 * Retorna si un determinado objeto obj colisiona con algun enemigo del mapaVisible suponiendo
	 * que el mismo se moviera hacia la direccion dir pasada por parametro. Retorna el enemigo que
	 * colisiono con el objeto, retorna null si no hay colision.
	 * 
	 * @param obj
	 *            - es el objeto a verificar colision.
	 * @param dir
	 *            - es la direccion en la cual obj quiere moverse.
	 * @return Retorna el enemigo que colisiono con el objeto.
	 */
	public Enemigo getColisionEnemigo(Objeto obj, int dir) {
		Rectangle r1 = new Rectangle(), r2 = new Rectangle();
		switch (dir) {
			case Constantes.dir_nada_left:
				return null;
			case Constantes.dir_nada_right:
				return null;
			case Constantes.dir_right:
				r1.setBounds(obj.getX() + 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_left:
				r1.setBounds(obj.getX() - 1, obj.getY(), obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_top:
				r1.setBounds(obj.getX(), obj.getY() - 1, obj.getSizeX(), obj.getSizeY());
				break;
			case Constantes.dir_bot:
				r1.setBounds(obj.getX(), obj.getY() + 1, obj.getSizeX(), obj.getSizeY());
				break;
		}

		try {
			for (int i=0;i<mapaV.getEnemigos().size();i++){
				if (mapaV.getEnemigos().get(i) != null) {
					r2.setBounds(mapaV.getEnemigos().get(i).getX(), mapaV.getEnemigos().get(i).getY(), mapaV.getEnemigos().get(i).getSizeX(), mapaV.getEnemigos().get(i).getSizeY());
					if (r1.intersects(r2))
						return mapaV.getEnemigos().get(i);
				}

			}
		} catch (java.util.ConcurrentModificationException e1) {
			System.out.println("error en control colisiones: getColisionEnemigo()");
		}
		return null;
	}

	public MapaVisible getMapaVisible() {
		return mapaV;
	}

}
